﻿#region

using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Collections;

#endregion

namespace Rnwood.SmtpServer
{
    public class Server
    {
        private Thread _coreThread;
        private TcpListener _listener;
        public ConcurrentQueue<Message> MessageQueue;
        public MessageProcessor msgProcessor;

        /// <summary>
        /// Constructor for Server Class. Initializes the Message Processor Module and the
        /// incoming message queue.
        /// </summary>
        /// <param name="behaviour">Server settings defined in the form of a behaviour class</param>
        public Server(IServerBehaviour behaviour)
        {
            Behaviour = behaviour;
            msgProcessor = new MessageProcessor(this);
            MessageQueue = new ConcurrentQueue<Message>();
        }

        public IServerBehaviour Behaviour { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether the server is currently running.
        /// </summary>
        public bool IsRunning { get; private set; }

        /// <summary>
        /// Runs the server synchronously. This method blocks until the server is stopped.
        /// </summary>
        public void Run()
        {
            if (IsRunning)
                throw new InvalidOperationException("Already running");

            // Define a new listener for given IP Address and Port
            // IP Address 0.0.0.0 specifies all interfaces
            _listener = new TcpListener(Behaviour.IpAddress, Behaviour.PortNumber);
            _listener.Start();

            IsRunning = true;

            Core();
        }

        private void Core()
        {
            _coreThread = Thread.CurrentThread;

            // For each new connection that is established, start up a new worker thread
            // that will handle further communication with the client. A single client can
            // deliver multiple messages at once.
            try
            {
                while (true)
                {
                    TcpClient tcpClient = _listener.AcceptTcpClient();
                    new Thread(ConnectionThreadWork).Start(tcpClient);
                }
            }
            catch (SocketException e)
            {
                if (e.SocketErrorCode == SocketError.Interrupted)
                {
                    //normal - caused by _listener.Stop();
                }
                else
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Runs the server asynchronously. This method returns once the server has been started.
        /// To stop the server call the <see cref="Stop()"/> method.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">if the server is already running.</exception>
        public void Start()
        {
            if (IsRunning)
                throw new InvalidOperationException("Already running");

            _listener = new TcpListener(Behaviour.IpAddress, Behaviour.PortNumber);
            _listener.Start();

            IsRunning = true;

            // Running the "Core" method in a new thread makes sure that the call is 
            // asynchronous.
            new Thread(Core).Start();
        }


        /// <summary>
        /// Stops the running server. Any existing connections are continued.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">if the server is not running.</exception>
        public void Stop()
        {
            if (!IsRunning)
            {
                throw new InvalidOperationException("Not running");
            }

            IsRunning = false;
            _listener.Stop();

            // Wait for coreThread to exit
            _coreThread.Join();
        }

        /// <summary>
        /// Worker thread for a new connection
        /// </summary>
        /// <param name="tcpClient">TcpClient who initiated the connection</param>
        private void ConnectionThreadWork(object tcpClient)
        {
            Connection connection = new Connection(this, (TcpClient) tcpClient);
            connection.Start();
        }

    }

}